#include "i2c.h"

// 内部辅助操作
#define SCL_HIGH() GPIO_SetBits(I2C_Port, I2C_Pin_SCL)
#define SCL_LOW() GPIO_ResetBits(I2C_Port, I2C_Pin_SCL)
#define SDA_HIGH() GPIO_SetBits(I2C_Port, I2C_Pin_SDA)
#define SDA_LOW() GPIO_ResetBits(I2C_Port, I2C_Pin_SDA)
#define SCL_READ() GPIO_ReadInputDataBit(I2C_Port, I2C_Pin_SCL)
#define SDA_READ() GPIO_ReadInputDataBit(I2C_Port, I2C_Pin_SDA)

// 延时函数
#define I2C_Delay() mySYSTICK_delayUs(4)

// 切换SDA引脚为输出模式, 只在本模块内部使用
static void I2C_SdaOut()
{
    GPIO_InitTypeDef GPIO_initStruct = {0};
    // 此处是直接使用GPIO外设操控拉高拉低和读取, 不经过其他外设
    // 并且IIC总线特性要求必须是开漏输出
    GPIO_initStruct.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_initStruct.GPIO_Pin = I2C_Pin_SDA;
    GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(I2C_Port, &GPIO_initStruct);
}

// 切换SDA引脚为输入模式, 只在本模块内部使用
static void I2C_SdaIn()
{
    GPIO_InitTypeDef GPIO_initStruct = {0};
    // 此处是直接使用GPIO外设操控拉高拉低和读取, 不经过其他外设
    // 并且IIC总线特性要求必须是开漏输出
    GPIO_initStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_initStruct.GPIO_Pin = I2C_Pin_SDA;
    GPIO_Init(I2C_Port, &GPIO_initStruct);
}

static void I2C_SendAck(void)
{
    SCL_LOW();
    I2C_SdaOut();

    SDA_LOW(); // 成功应答, 给的数据是低电平
    I2C_Delay();
    SCL_HIGH();
    I2C_Delay();
    SCL_LOW();
}

// 无应答, 不是不应答, 依然需要主动发送时钟信号
static void I2C_SendNoAck(void)
{
    SCL_LOW();
    I2C_SdaOut();

    SDA_HIGH(); // 无应答, 给的数据是高电平
    I2C_Delay();
    SCL_HIGH();
    I2C_Delay();
    SCL_LOW();
}

// -----------------------------------------------------
void myI2C_Init(void)
{
    //  RCC
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    // GPIO配置
    // 默认设置为输出
    GPIO_InitTypeDef gpio_initStruct = {0};
    // 此处是直接使用GPIO外设操控拉高拉低和读取, 不经过其他外设
    // 并且IIC总线特性要求必须是开漏输出
    gpio_initStruct.GPIO_Mode = GPIO_Mode_Out_OD;
    gpio_initStruct.GPIO_Pin = I2C_Pin_SCL | I2C_Pin_SDA;
    gpio_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(I2C_Port, &gpio_initStruct);
}

void I2C_Start(void)
{
    // 准备阶段: 将SCL拉低的情况下, 调整SDA输出
    SCL_LOW();
    I2C_SdaOut();
    SDA_HIGH();
    SCL_HIGH();

    I2C_Delay();
    SDA_LOW();
    I2C_Delay();
    // 钳住时钟线, 确保其他主机无法获取时钟
    SCL_LOW();
}

void I2C_Stop(void)
{
    SCL_LOW();
    I2C_SdaOut();
    SDA_LOW();
    SCL_HIGH();

    I2C_Delay();
    SDA_HIGH();
}

void I2C_SendByte(uint8_t data)
{
    SCL_LOW();
    I2C_SdaOut();

    for (int i = 0; i < 8; i++)
    {
        // 判断待发送的是1还是0, 来拉高拉低SDA
        if (data & 0x80)
            SDA_HIGH();
        else
            SDA_LOW();
        // 产生一个时钟波形, 控制从机接收
        SCL_HIGH();
        I2C_Delay();
        SCL_LOW();
        // 将数据左移以为
        data <<= 1;
    }
}

uint8_t I2C_RecvByte(I2C_ACKTypeDef flagAck)
{
    uint8_t data = 0;
    SCL_LOW();
    I2C_SdaIn();

    // 先产生一个SCL上升沿, 表示准备接收数据
    // 然后SDA读取一个bit
    // 再产生一个SCL下降沿, 表示读取完成, 对方可以准备下一个bit
    for (int i = 0; i < 8; i++)
    {
        // 需要先左移一次, 防止最后一次bit收完后多余左移
        data <<= 1;
        // 拉高并接收,保存到data最低位
        SCL_HIGH();
        if (SDA_READ())
            data |= 0x01;
        // 然后拉低并延时, 表示对方可以准备下一个bit
        SCL_LOW();
        I2C_Delay();
    }
    if(flagAck)
        I2C_SendAck();
    else
        I2C_SendNoAck();
    return data;
}

I2C_ACKTypeDef I2C_WaitForAck(void)
{
    uint8_t count = 4;

    SCL_LOW();
    I2C_SdaIn();

    SCL_HIGH();
    while(SDA_READ()) // 读取并判断, 如果是高电平, 继续等待
    {
        I2C_Delay();
        if(0 == --count)
        {
            I2C_Stop();
            return I2C_NOACK;
        }
    }
    SCL_LOW();
    return I2C_ACK;
}
